package org.hefan.hrm.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.hefan.hrm.domain.CourseType;
import org.hefan.hrm.exception.ErrorCode;
import org.hefan.hrm.exception.ValidUtil;
import org.hefan.hrm.mapper.CourseTypeMapper;
import org.hefan.hrm.service.ICourseTypeService;
import org.hefan.hrm.vo.CrumbsVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author HeFan
 * @since 2022-01-06
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    @Autowired
    private CourseTypeMapper courseTypeMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    private static  final  String COURSETYPE_TREEDATA_IN_CACHE = "courseType_treeData_in_cache";//redis的key

    @Override
    public List<CourseType> treeData(long pid) {
        //加入缓存逻辑
        // 从redis中获取key为COURSETYPE_TREEDATA_IN_CACHE的数据
        List<CourseType> courseTypes = (List<CourseType>) redisTemplate.opsForValue().get(COURSETYPE_TREEDATA_IN_CACHE);
        // 如果不等于null,也就是从redis中获取到了,则直接返回
        if (courseTypes != null) {
            return courseTypes;
        } else {
            // 没有获取到,也就是redis中没有数据
            // 则会进行查数据库查询数据,然后把数据放到redis内存中
            // 假设测试有500w请求
            synchronized (this.getClass()) {
                // 先从redis内存中获取数据
                courseTypes = (List<CourseType>) redisTemplate.opsForValue().get(COURSETYPE_TREEDATA_IN_CACHE);
                // 如果redis内存中没有数据
                if (courseTypes == null) {
                    // 第一个请求会进来
                    // 通过方法loadTreeDataByLood从数据库查到数据
                    List<CourseType> courseTypesFromDb = loadTreeDataByLood(pid);
                    // 1 缓存穿透，方案1:没有数据也放一个null（实现了），方案2：布隆过滤器
                    // 2 缓存击穿，缓存过期一瞬间来高并发。 方案1：永不过期（实现了） 方案2：单机锁（不支持集群）（实现了） //@TODO 方案3：分布式锁（支持集群）
                    // 3 缓存雪崩 ，方案1：缓存永不过期，缓存预热（实现了） 方案2：让各个缓存数据过期时间不一样

                    // 把COURSETYPE_TREEDATA_IN_CACHE作为k值,从数据库查到的数据courseTypesFromDb作为value放入redis内存中
                    redisTemplate.opsForValue().set(COURSETYPE_TREEDATA_IN_CACHE, courseTypesFromDb);
                    // 返回最新的从数据库查询的数据
                    return courseTypesFromDb;
                }
                // 走这里则是redis中有最新数据,直接返回.
                return  courseTypes;
            }
        }
    }

    /**
     * 面包屑
     * @param typeId
     * @return
     */
    @Override
    public List<CrumbsVo> queryCrumbs(Long typeId) {
        //0 定义返回类型List
        List<CrumbsVo> list = new ArrayList<>();
        //1 校验
        ValidUtil.assertNotNull(typeId, ErrorCode.CODE_400_COURSE_TYPE_CRUMBS_ILLEGAL);
        //2 通过typeId获取Type进而获取path
        String path = courseTypeMapper.selectById(typeId).getPath();
        //3 通过.分割得到typeIds [1,2,3]
        String[] typeIds = path.split("\\.");
        //4 遍历，每个节点构造CrumbsVo放过入List
        for (String idStr : typeIds) {
            CrumbsVo crumbsVo = new CrumbsVo();
            Long id = Long.valueOf(idStr);
            //4.1通过typeId,查询课程可以构造自己                          1 2 3
            CourseType owner = courseTypeMapper.selectById(id);
            crumbsVo.setOwnerCourseType(owner);
            //4.2 通过自己的pid获取所有的儿子排除自己
            Long pid = owner.getPid();
            List<CourseType> otherCourseTypes = courseTypeMapper
                    .selectList(new EntityWrapper<CourseType>()
                            .eq("pid", pid));
            // 迭代器
            Iterator<CourseType> iterator = otherCourseTypes.iterator();
            while (iterator.hasNext()) {
                CourseType courseType = iterator.next();
                if (id.longValue() == courseType.getId().longValue()) {
                    iterator.remove();
                    break;
                }
            }
            crumbsVo.setOtherCourseTypes(otherCourseTypes);
            list.add(crumbsVo);
        }
        return list;
    }

    /**
     * 循环方案
     * @param pid
     * @return
     */
    private List<CourseType> loadTreeDataByLood(Long pid) {
        // 1 定义返回列表
        ArrayList<CourseType> result = new ArrayList<>();
        // 2 查询所有节点
        List<CourseType> allCourseTypes = courseTypeMapper.selectList(null);
        // new一个hashmap
        HashMap<Long, CourseType> courseTypeDto = new HashMap<>();
        // 遍历从数据库查到的数据,把遍历出来的每一个数据的id作为key值,遍历出来的每一个数据作为value放到map里
        allCourseTypes.forEach(courseType -> {
            courseTypeDto.put(courseType.getId(), courseType);
        });
        // 3 遍历所有节点判断是否是一级节点 - 当前类型pid是传入pid
        allCourseTypes.forEach(courseType -> {
            // 3.1 如果是放入返回列表
            Long ppid = courseType.getPid();
            /*
            pid是前端传来的  ppid是courseType的pid属性
            对比pid和ppid
             */
            if (ppid.longValue() == pid.longValue()) {
                result.add(courseType);
            // 3.2 如果不是，放入父亲儿子集合
            } else {
                // 转换为map再来获取数据 O(2n)
                CourseType parent = courseTypeDto.get(ppid);
                // 获取到父亲儿子集合，把自己作为一个儿子放进去
                parent.getChildren().add(courseType);
            }
        });
        //4 返回“返回列表”
        return result;
    }

    @Override
    public boolean insert(CourseType entity) {
        // 数据发生变更，清除redis中缓存，下来查询重新放缓存并返回
        super.insert(entity);
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        return true;
    }

    @Override
    public boolean updateById(CourseType entity) {
        super.updateById(entity);
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        return true;
    }

    @Override
    public boolean deleteById(Serializable id) {
        super.deleteById(id);
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHE);
        return true;
    }
}
